// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.

using System;
using System.Diagnostics;
using System.Threading;

namespace Kailua.net.windward.utils
{
	/// <summary>
	/// Returns the results of a RunProgram call.
	/// </summary>
	public class ProgramResults
	{

		private readonly string standardOutput;
		private readonly string standardError;
		private readonly int returnCode;

		internal ProgramResults(string standardOutput, string standardError, int returnCode)
		{
			this.standardOutput = standardOutput;
			this.standardError = standardError;
			this.returnCode = returnCode;
		}

		/// <summary>
		/// The standard output of the executed program.
		/// </summary>
		public string StandardOutput
		{
			get { return standardOutput; }
		}

		/// <summary>
		/// The standard error of the executed program.
		/// </summary>
		public string StandardError
		{
			get { return standardError; }
		}

		/// <summary>
		/// The return code of the executed program.
		/// </summary>
		public int ReturnCode
		{
			get { return returnCode; }
		}
	}

	/// <summary>
	/// Run an external program.
	/// </summary>
	public class RunProgram : IDisposable
	{

		private readonly string program;
		private readonly string commandLine;
		private Process proc;
		private string output;
		private string error;

		private RunProgram(string program, string commandLine)
		{
			this.program = program;
			this.commandLine = commandLine;
		}

		/// <summary>
		/// Dispose of objects
		/// </summary>
		public void Dispose()
		{

			if (proc != null)
			{
				proc.Dispose();
				proc = null;
			}
		}

		private ProgramResults Run()
		{

			proc = new Process();
			proc.StartInfo.FileName = program;
			proc.StartInfo.Arguments = commandLine;
			proc.StartInfo.CreateNoWindow = true;
			proc.StartInfo.UseShellExecute = false;
			proc.StartInfo.RedirectStandardOutput = true;
			proc.StartInfo.RedirectStandardError = true;

			Thread thrdReadStandardError = new Thread(ReadStandardError);
			Thread thrdReadStandardOut = new Thread(ReadStandardOut);

			// start it
			proc.Start();
			thrdReadStandardError.Start();
			thrdReadStandardOut.Start();

			// finish up everything
			proc.WaitForExit();
			thrdReadStandardError.Join();
			thrdReadStandardOut.Join();

			ProgramResults rslts = new ProgramResults(output, error, proc.ExitCode);

			// clean up - probably a pretty heavy weight object
			proc.Close();
			proc.Dispose();

			return rslts;
		}

		private void ReadStandardError()
		{
			error = proc.StandardError.ReadToEnd();
		}

		private void ReadStandardOut()
		{
			output = proc.StandardOutput.ReadToEnd();
		}

		/// <summary>
		/// Run the passed in program and return when the program completes.
		/// </summary>
		/// <param name="program">The filename of the program.</param>
		/// <param name="commandLine">The command line of the program.</param>
		/// <returns>The standard out, error, and return code of the completed program.</returns>
		public static ProgramResults Run(string program, string commandLine)
		{

			RunProgram proc = new RunProgram(program, commandLine);
			return proc.Run();
		}
	}
}